/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package edu.ieslaferreria.creditsintesis;

import com.jme.app.SimpleGame;
import com.jme.bounding.BoundingBox;
import com.jme.input.ChaseCamera;
import com.jme.input.thirdperson.ThirdPersonMouseLook;
import com.jme.math.FastMath;
import com.jme.math.Vector3f;
import com.jme.scene.Node;
import com.jme.scene.TriMesh;
import com.jme.scene.shape.Box;
import com.jmex.terrain.TerrainPage;
import edu.ieslaferreria.creditsintesis.builders.CaveMazeTerrain;
import edu.ieslaferreria.creditsintesis.builders.PlaneTerrain;
import edu.ieslaferreria.creditsintesis.builders.RandomTerrainManager;
import edu.ieslaferreria.creditsintesis.builders.Sky;
import edu.ieslaferreria.creditsintesis.builders.TerrainManager;
import edu.ieslaferreria.creditsintesis.controllers.InteractionController;
import edu.ieslaferreria.creditsintesis.controllers.UserActionController;
import edu.ieslaferreria.creditsintesis.controllers.WorldController;
import edu.ieslaferreria.creditsintesis.logicgame.ActionStates;
import edu.ieslaferreria.creditsintesis.logicgame.ActionStatesHash1;
import edu.ieslaferreria.creditsintesis.logicgame.FirstExplorer;
import edu.ieslaferreria.creditsintesis.logicgame.ObjectWithStates;
import edu.ieslaferreria.creditsintesis.logicgame.Parameter;
import edu.ieslaferreria.creditsintesis.logicgame.Player;
import edu.ieslaferreria.creditsintesis.logicgame.actions.ChangedLocationEvent;
import edu.ieslaferreria.creditsintesis.logicgame.actions.ConfigurationEvent;
import edu.ieslaferreria.creditsintesis.logicgame.actions.ConfigurationMapEvent;
import edu.ieslaferreria.creditsintesis.logicgame.actions.RmiEvent;
import edu.ieslaferreria.creditsintesis.logicgame.statemachineimpl.FirstExplorerStateMachine;
import edu.ieslaferreria.creditsintesis.logicgame.statemachineimpl.GenericStateMachine;
import edu.ieslaferreria.creditsintesis.models.NodeModel;
import edu.ieslaferreria.creditsintesis.models.TriMeshModel;
import edu.ieslaferreria.creditsintesis.rmi.Client;
import edu.ieslaferreria.creditsintesis.rmi.EventAddRemotePlayerListener;
import edu.ieslaferreria.creditsintesis.rmi.EventConfiguracioListener;
import edu.ieslaferreria.creditsintesis.rmi.EventLocationListener;
import edu.ieslaferreria.creditsintesis.rmi.EventStateListener;
import edu.ieslaferreria.creditsintesis.rmi.GameEvent;
import edu.ieslaferreria.creditsintesis.rmi.MapConfiguracioListener;
import java.net.ConnectException;
import java.rmi.RemoteException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Esta clase crea las cámaras, el entorno, los objetos y todo lo necesario para
 * que el juego funcione en RMI, por tanto crea tanto los objetos propios del
 * jugador 1 (servidor), como los objetos remotos del jugador 2, 3, etc...
 *
 * @author Administrador
 */
public class MainPlayerRmi extends SimpleGame{
    private Vector3f camPos = new Vector3f();
    protected ActionStates actionStates = new ActionStatesHash1();
    protected WorldController worldController;
    protected TerrainPage terrain;
    protected Sky sky;
    protected ChaseCamera chaser;
    protected InteractionController interactionController;
    protected HashMap<String,Node> remoteObjects= new HashMap<String, Node>();
    protected TerrainManager terrainManager = null;
    protected Player player;
    protected UserActionController userActionController;
    protected Client client;


    @Override
    protected void simpleUpdate() {

        try {
            float interpolation = timer.getTimePerFrame();
            camPos.x = cam.getLocation().x;
            camPos.y = terrain.getHeight(cam.getLocation()) + 10;
            camPos.z = cam.getLocation().z;
            cam.setLocation(camPos);
            sky.update();
            for(Iterator<Node> iter=remoteObjects.values().iterator();iter.hasNext();){
                Node n=iter.next();
                n.setLocalTranslation(n.getLocalTranslation());
                n.updateGeometricState(interpolation, true);
            }

            //update the chase camera to handle the player moving around.
            chaser.update(interpolation);
            interactionController.update(interpolation);

        } catch (ConnectException ex){
            System.out.println(ex.getMessage());
        } catch (RemoteException ex) {
            Logger.getLogger(MainPlayer1Rmi.class.getName()).log(Level.SEVERE, null, ex);
        }

        }

    @Override
    protected void simpleRender() {
         sky.render();
	}

    @Override
    protected void simpleInitGame() {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    protected void buildRemoteEnvironment(Node rootNode, String name,int blocksize, int size,Vector3f vector3f,float[] map,String modelTerrainName) {
        lightState.setTwoSidedLighting(true);

//        terrainManager = new RandomTerrainManager("terrain");
//        terrainManager = (TerrainManager) Class.forName(name).newInstance();
        if(modelTerrainName.equals("PlaneTerrain")){
            terrainManager = new PlaneTerrain();
        }else if(modelTerrainName.equals("CaveMazeTerrain")){
            terrainManager = new CaveMazeTerrain();
        }else if(modelTerrainName.equals("RandomTerrain")){
            terrainManager = new RandomTerrainManager(name);
        }
        terrain = terrainManager.buildRemoteTerrain(rootNode,name,blocksize, size,vector3f,map,modelTerrainName);
        sky = terrainManager.buildSky(rootNode, terrain);
        worldController.setTerrainPage(terrain);
//        worldController.setLimits(new Vector3f(150, 0, 150));
        worldController.setLimits(new Vector3f(300, 0, 300));
        actionStates.setEventMap(terrainManager.getEvent());
    }

    protected void buildEnvironment(Node rootNode, String name,int blocksize, int size,Vector3f vector3f,String modelTerrainName){
        lightState.setTwoSidedLighting(true);

//        terrainManager = new RandomTerrainManager("terrain");
        if(modelTerrainName.equals("PlaneTerrain")){
            terrainManager = new PlaneTerrain();
        }else if(modelTerrainName.equals("CaveMazeTerrain")){
            terrainManager = new CaveMazeTerrain();
        }else if(modelTerrainName.equals("RandomTerrain")){
            terrainManager = new RandomTerrainManager(name);
        }
        terrain = terrainManager.buildTerrain(rootNode,name,blocksize, size,vector3f,modelTerrainName);
        sky = terrainManager.buildSky(rootNode, terrain);
        worldController.setTerrainPage(terrain);
//        worldController.setLimits(new Vector3f(150, 0, 150));
        worldController.setLimits(new Vector3f(300, 0, 300));
        actionStates.setEventMap(terrainManager.getEvent());
    }

//    De momento creamos el player siempre con la misma escala
//    y con el mismo tipo de ModelBound, pero lo cambiaremos en próximas versiones

    protected void createClient (String name) throws RemoteException{
        client=new Client(name);

            client.setListenerConfiguracio(new EventConfiguracioListener() {

                public void action(RmiEvent event) throws RemoteException {
                    buildRemoteObject(event);
                }
            });

           client.setListenerLocation(new EventLocationListener() {

                public void action(RmiEvent event) throws RemoteException {
                    ChangedLocationEvent evt =  (ChangedLocationEvent) event;
                    Node obj = remoteObjects.get(evt.getName());
                    if(obj==null){
                        return;
                    }
                    obj.setLocalTranslation(evt.getVector3f());
                    obj.setLocalRotation(evt.getNewQuaternion());
                    System.out.println("RemoteObject x:" + obj.getLocalTranslation().x +
                                       " y:" + obj.getLocalTranslation().y +
                                       " z:" + obj.getLocalTranslation().z);
                    }
            });
            client.setListenerState(new EventStateListener() {

                public void action(RmiEvent event) throws RemoteException {
                    throw new UnsupportedOperationException("Not supported yet.");
                }
            });
            client.setListenerRemotePlayer(new EventAddRemotePlayerListener() {

                public void action(RmiEvent event) throws RemoteException {
//                    AddRemotePlayerEvent evt =  (AddRemotePlayerEvent) event;
                    buildRemoteObject(event);
                }
            });
            client.setListenerMapConfig(new MapConfiguracioListener() {

            public void action(RmiEvent event) throws RemoteException {
                ConfigurationMapEvent evt =  (ConfigurationMapEvent) event;
                buildRemoteEnvironment(rootNode,evt.getModelMapName(),
                        (Integer)evt.getParameters().get("Blocksize").getSerializable(),
                        (Integer)evt.getParameters().get("Size").getSerializable(),
                        (Vector3f)evt.getParameters().get("Vector3f").getSerializable(),
                        (float[])evt.getParameters().get("Map").getSerializable(),
                        evt.getModelMapName());
            }
        });

    }

    protected void buildRemoteObject(GameEvent evt){
        ConfigurationEvent event = (ConfigurationEvent) evt;
        System.out.println(event.getModelBoundName());
        if(event.getModelBoundName().equals("TreeModel")){
            Node b = NodeModel.loadModel("model/tree.obj");
            b.setLocalScale(event.getScale());
            ObjectWithStates n = new ObjectWithStates(event.getName(),actionStates);
            n.setLocalTranslation((Vector3f)event.getParameters().get("Vector3f").getSerializable());
            rootNode.attachChild(n);
            n.attachChild(b);
            n.updateWorldBound();

            remoteObjects.put(event.getName(), n);
            actionStates.addObject(n);
            n.propagateBoundToRoot();
            rootNode.updateWorldBound();
        }else if(event.getModelBoundName().equals("rabbitModel")){
            TriMesh b = TriMeshModel.loadModel("model/conejo.obj");
            b.setLocalScale(event.getScale());
            ObjectWithStates n = new ObjectWithStates(event.getName(),actionStates);
            n.setLocalTranslation((Vector3f)event.getParameters().get("Vector3f").getSerializable());
            rootNode.attachChild(n);
            n.attachChild(b);
            n.updateWorldBound();

            remoteObjects.put(event.getName(), n);
            actionStates.addObject(n);
            n.propagateBoundToRoot();
            rootNode.updateWorldBound();
        }else if(event.getModelBoundName().equals("TouristModel")){
            Node b = NodeModel.loadModel("model/tourist.obj");
            b.setLocalScale(event.getScale());
            ObjectWithStates n = new ObjectWithStates(event.getName(),actionStates);
            n.setLocalTranslation((Vector3f)event.getParameters().get("Vector3f").getSerializable());
            rootNode.attachChild(n);
            n.attachChild(b);
            n.updateWorldBound();

            remoteObjects.put(event.getName(), n);
            actionStates.addObject(n);
            n.propagateBoundToRoot();
            rootNode.updateWorldBound();
        }else if(event.getModelBoundName().equals("ZackModel")){
            Node b = NodeModel.loadModel("model/zack.obj");
            b.setLocalScale(event.getScale());
            ObjectWithStates n = new ObjectWithStates(event.getName(),actionStates);
            n.setLocalTranslation((Vector3f)event.getParameters().get("Vector3f").getSerializable());
            rootNode.attachChild(n);
            n.attachChild(b);
            n.updateWorldBound();

            remoteObjects.put(event.getName(), n);
            actionStates.addObject(n);
            n.propagateBoundToRoot();
            rootNode.updateWorldBound();
        }else{
            Box b = new Box(event.getName()+ " Box",new Vector3f(),
                           (Float)event.getParameters().get("xExtend").getSerializable(),
                           (Float)event.getParameters().get("yExtend").getSerializable(),
                           (Float)event.getParameters().get("zExtend").getSerializable());
            b.setModelBound(new BoundingBox());
            b.updateModelBound();
            b.setLocalScale(event.getScale());
            ObjectWithStates n = new ObjectWithStates(event.getName(),actionStates);
            n.setLocalTranslation((Vector3f)event.getParameters().get("Vector3f").getSerializable());
            rootNode.attachChild(n);
            n.attachChild(b);
            n.updateWorldBound();


            remoteObjects.put(event.getName(), n);
            actionStates.addObject(n);
            n.propagateBoundToRoot();
            rootNode.updateWorldBound();
        }
    }
    protected void buildChaseCamera() {
        Vector3f targetOffset = new Vector3f();
        //targetOffset.y = ((BoundingBox) player.getWorldBound()).yExtent * 1f;
        HashMap props = new HashMap();
        props.put(ThirdPersonMouseLook.PROP_MAXROLLOUT, "100");
        props.put(ThirdPersonMouseLook.PROP_MINROLLOUT, "100");
        props.put(ChaseCamera.PROP_TARGETOFFSET, targetOffset);
        //props.put(ThirdPersonMouseLook.PROP_MAXASCENT, ""+60 * FastMath.DEG_TO_RAD);
        props.put(ThirdPersonMouseLook.PROP_MAXASCENT, ""+ 30 * FastMath.DEG_TO_RAD);
        props.put(ThirdPersonMouseLook.PROP_MINASCENT, ""+ 30 * FastMath.DEG_TO_RAD);
        //props.put(ChaseCamera.PROP_INITIALSPHERECOORDS, new Vector3f(100, 0, 45* FastMath.DEG_TO_RAD));
        props.put(ChaseCamera.PROP_INITIALSPHERECOORDS, new Vector3f(100, 0, 30* FastMath.DEG_TO_RAD));
        //props.put(ChaseCamera.PROP_TARGETOFFSET, targetOffset);
        chaser = new ChaseCamera(cam, player, props);
        //chaser = new ChaseCamera(cam, remoteObjects.get("Player1"), props);
        chaser.setMaxDistance(50);
        chaser.setMinDistance(50);
        //chaser.setMaxDistance(0);
        //chaser.setMinDistance(0);
    }

// creamos objetos fijos como árboles, con escala y posicion determinada,
//    en próximas versiones cambiaremos el tipo de objeto
    protected ObjectWithStates buildTree(String name, Vector3f vector3f) throws RemoteException {
            ObjectWithStates obj = new ObjectWithStates(name,new GenericStateMachine(), actionStates);
            float scale = 0.4f;
            Node s1 = NodeModel.loadModel("model/tree.obj");
            s1.setLocalScale(scale);
//            s1.setModelBound(new BoundingBox(new Vector3f(), 0.35f, 0.25f, 0.5f));
//            s1.updateModelBound();

//            Box s1 = new Box("Box" + name, new Vector3f(), 0.35f,0.25f,0.5f);
//            s1.setModelBound(new BoundingBox());
//            s1.updateModelBound();
//            s1.setLocalScale(scale);
            obj.setLocalTranslation(vector3f);
            rootNode.attachChild(obj);
            obj.attachChild(s1);
            obj.updateWorldBound();
            obj.setMainsParameters("TreeModel", scale);
            obj.addParameter("Vector3f",new Parameter(obj.getLocalTranslation()));
            obj.addParameter("xExtend", new Parameter(0.35f));
            obj.addParameter("yExtend", new Parameter(0.25f));
            obj.addParameter("zExtend", new Parameter( 0.5f));

            float characterMinHeight = terrain.getHeight(obj.getLocalTranslation());
//                    +((BoundingBox)obj.getWorldBound()).yExtent;
                if (!Float.isInfinite(characterMinHeight) && !Float.isNaN(characterMinHeight)) {
                    obj.getLocalTranslation().y = characterMinHeight;
                }
          actionStates.addObject(obj);
          return obj;
//        actionStates.getObjectsToValidateState().add(fixedBox);
//        actionStates.getObjectsToValidateState().add(fixedBox2);
        }

    protected void buildPlayer(String playerName, Vector3f vector3f, float scale, String modelPath,
                                String model,float offSetPos,float height) throws RemoteException {
            Node s1 = NodeModel.loadModel(modelPath);
            s1.setLocalScale(scale);
            player = new Player(playerName,0,worldController,actionStates);
            player.setLocalTranslation(vector3f);
            rootNode.attachChild(player);
            player.attachChild(s1);
            player.updateWorldBound();
            player.setLinealVelocity(20f);
            player.setRotationVelocity(5f);
            userActionController.setPlayer(player);
            player.setMainsParameters(model, scale);
            player.setOffsetPosition(offSetPos);
            player.setHeight(height);
            player.addParameter("Vector3f",new Parameter(player.getLocalTranslation()));
            player.addParameter("xExtend", new Parameter(0.35f));
            player.addParameter("yExtend", new Parameter(0.25f));
            player.addParameter("zExtend", new Parameter( 0.5f));
            actionStates.addObject(player);

//            Box b = new Box("Box"+playerName, new Vector3f(), 0.35f,0.25f,0.5f);
//            b.setModelBound(new BoundingBox());
//            b.updateModelBound();
//            b.setLocalScale(scale);
//            player = new Player(playerName,0,worldController,actionStates);
//            player.setLocalTranslation(vector3f);
//            rootNode.attachChild(player);
//            player.attachChild(b);
//            player.updateWorldBound();
//            player.setLinealVelocity(20f);
//            player.setRotationVelocity(5f);
//            actionStates.addObject(player);
//            userActionController.setPlayer(player);
//            player.setMainsParameters("BoundingBox", scale);
//            player.setOffsetPosition( ((BoundingBox)player.getWorldBound()).yExtent);
//            player.setMainsParameters("ModelBoundingBox", scale);
//            player.addParameter("Vector3f",new Parameter(player.getLocalTranslation()));
//            player.addParameter("xExtend", new Parameter(0.35f));
//            player.addParameter("yExtend", new Parameter(0.25f));
//            player.addParameter("zExtend", new Parameter(0.5f));
            worldController.levelingWithTerrain(player);

        }
        public FirstExplorer buildFirstExplorerObjects(String name,String modelname, String modelpath, float scale, float height, float offSetPos, Vector3f vector3f) throws RemoteException {
            FirstExplorer fExp;
            TriMesh s1 = TriMeshModel.loadModel(modelpath);
            s1.setLocalScale(scale);
            fExp = new FirstExplorer(name, worldController, 1, actionStates);
            fExp.setLocalTranslation(vector3f);
            rootNode.attachChild(fExp);
            fExp.attachChild(s1);
            fExp.updateWorldBound();
            fExp.setOffsetPosition(offSetPos);
            fExp.setHeight(height);
            fExp.setMainsParameters(modelname, scale);
            fExp.addParameter("Vector3f",new Parameter(fExp.getLocalTranslation()));
            fExp.addParameter("Height",new Parameter(fExp.getOffsetPosition()));

            worldController.levelingWithTerrain(fExp);
 //           fExp.processTransition(FirstExplorerStateMachine.ST_TO_FORWARD);

            actionStates.addObject(fExp);
            fExp.processTransition(FirstExplorerStateMachine.TR_FREE_WAY);
            return fExp;


        }



}
